package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"fx_swift_schedule/api"
	"fx_swift_schedule/internal/model"
	"fx_swift_schedule/pkg/code"
	"fx_swift_schedule/pkg/global"
	"fx_swift_schedule/pkg/gorsa"
	"fx_swift_schedule/pkg/httputils"
	"fx_swift_schedule/pkg/internalerrors"
	"fx_swift_schedule/pkg/logger"
	"fx_swift_schedule/pkg/mail"
	"fx_swift_schedule/pkg/temple"
	"fx_swift_schedule/pkg/utils"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/golang-module/carbon/v2"
	"github.com/jinzhu/copier"
	"github.com/spf13/cast"
	"net/http"
	"strings"
	"time"
)

type ICardCallBackSrv interface {
	// 持卡人状态修改
	GetCardHolderInfo(ctx context.Context) error
	// 卡交易结果通知
	CardTransaction(ctx context.Context, encryptedStream string) error
	// 定时查询消费清算数据
	CardSettlementInfo(ctx context.Context, startTime, endTime string) error
	// 手动触发下游失败回调
	ManualSendCallBack(ctx context.Context) error
	// 手动补充持卡人对应卡头信息
	ManualOperatorCardHeader(ctx context.Context) error
}

type cardCallBackSrv struct {
}

func NewCardCallBackSrv() ICardCallBackSrv {
	return &cardCallBackSrv{}
}

// 持卡人状态修改
func (c *cardCallBackSrv) GetCardHolderInfo(ctx context.Context) error {
	var (
		err            error
		url            = "/api/vas/cardhodler/info"
		dataContent    string
		pubdecrypt_str string
		resp           *api.GetCardHolderRes
		cardHolderList []*model.FxCardholder
	)

	if err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_cardholder").
		Where("holder_status='3'").Scan(&cardHolderList); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if len(cardHolderList) > 0 {
		for _, cardObj := range cardHolderList {

			dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, &api.GetCardHolderReq{
				CardUserId: cardObj.CardUserId,
			})
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			// 公钥解密
			pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			if len(resp.HolderCardBinsInfo) == 0 {
				if resp.Status != 3 {
					if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_cardholder").Data(g.Map{
						"holder_status": resp.Status,
					}).Where("card_user_id=?", cardObj.CardUserId).Update(); err != nil {
						return internalerrors.New(code.ErrUnknown, err.Error())
					}
				}
			} else {
				if resp.HolderCardBinsInfo[0].HolderStatus != resp.Status {
					if resp.HolderCardBinsInfo[0].HolderStatus != 3 {
						if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_cardholder").Data(g.Map{
							"holder_status": resp.HolderCardBinsInfo[0].HolderStatus,
						}).Where("card_user_id=?", cardObj.CardUserId).Update(); err != nil {
							return internalerrors.New(code.ErrUnknown, err.Error())
						}
						if resp.HolderCardBinsInfo[0].HolderStatus != 0 {
							cardHeadArray := make([]string, 0)
							for _, value := range resp.HolderCardBinsInfo {
								cardHeadArray = append(cardHeadArray, value.CardBin)
							}
							if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_holder_head").Insert(&model.FxHolderHead{
								CardUserId:  resp.CardUserId,
								FirstName:   resp.FirstName,
								LastName:    resp.LastName,
								CountryCode: resp.CountryCode,
								CardHead:    strings.Join(cardHeadArray, ","),
							}); err != nil {
								return internalerrors.New(code.ErrUnknown, err.Error())
							}
						}
					}

				} else {
					if resp.Status != 3 {
						if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_cardholder").Data(g.Map{
							"holder_status": resp.Status,
						}).Where("card_user_id=?", cardObj.CardUserId).Update(); err != nil {
							return internalerrors.New(code.ErrUnknown, err.Error())
						}
						if resp.Status != 0 {
							cardHeadArray := make([]string, 0)
							for _, value := range resp.HolderCardBinsInfo {
								cardHeadArray = append(cardHeadArray, value.CardBin)
							}
							if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_holder_head").Insert(&model.FxHolderHead{
								CardUserId:  resp.CardUserId,
								FirstName:   resp.FirstName,
								LastName:    resp.LastName,
								CountryCode: resp.CountryCode,
								CardHead:    strings.Join(cardHeadArray, ","),
							}); err != nil {
								return internalerrors.New(code.ErrUnknown, err.Error())
							}
						}
					}
				}
			}
		}
	}
	return nil
}

// 卡结果通知
func (c *cardCallBackSrv) CardTransaction(ctx context.Context, encryptedStream string) error {
	var (
		err            error
		recordNum      int
		cardIsExitNum  int
		orderRecordNum int
		pubdecrypt_str string
		jsonByte       []byte
		resp           *api.CardTransactionReq
	)
	// 公钥解密
	pubdecrypt_str, err = gorsa.PublicDecrypt(encryptedStream, gorsa.GetPublicKeyStr())
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if resp.Type == "card_transaction" { // 卡交易结果
		logger.SugarLogger.Info("卡交易结果通知开始.......")
		transObj := &api.CardTransactionInfo{}
		jsonByte, err = json.Marshal(resp.Data)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		err = json.Unmarshal(jsonByte, &transObj)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		g.Dump(transObj)
		// 判断此卡是否是我们平台所开
		cardIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
			Model("fx_card_info").Where("card_id=?", transObj.CardId).Count()
		if cardIsExitNum > 0 {
			recordNum, err = global.GFDB.Schema("card").Model("fx_card_trade_record").
				Where("record_no=?", transObj.RecordNo).Count()
			if recordNum == 0 {
				if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_card_trade_record").Insert(&model.FxCardTradeRecord{
					RecordNo:             transObj.RecordNo,
					CardId:               transObj.CardId,
					OccurTime:            transObj.OccurTime,
					TransCurrency:        transObj.TransCurrency,
					TransCurrencyAmt:     cast.ToString(transObj.TransCurrencyAmt),
					LocalCurrency:        transObj.LocalCurrency,
					LocalCurrencyAmt:     cast.ToString(transObj.LocalCurrencyAmt),
					RespCode:             transObj.RespCode,
					RespCodeDesc:         transObj.RespCodeDesc,
					MerchantName:         transObj.MerchantName,
					MerchantCategoryCode: transObj.MerchantCategoryCode,
					CrossBoardType:       transObj.CrossBoardType,
					TransType:            transObj.TransType,
					TransStatus:          transObj.TransStatus,
				}); err != nil {
					return internalerrors.New(code.ErrUnknown, err.Error())
				}
				if err == nil {
					// 发送给下游回调
					transObjVo := &api.CardTransactionInfoVo{}
					copier.Copy(&transObjVo, &transObj)
					operatorCallBack(ctx, transObjVo, transObjVo.CardId, resp.Type)
				}
			}
			logger.SugarLogger.Info("卡交易结果通知结束.......")
		} else {
			logger.SugarLogger.Infof("此卡：%s不存在与我们平台", transObj.CardId)
		}
	} else if resp.Type == "type_card_operate" { // 订单结果通知
		logger.SugarLogger.Info("订单结果通知开始.......")
		cardOrderObj := &api.CardOrderInfo{}
		jsonByte, err = json.Marshal(resp.Data)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		err = json.Unmarshal(jsonByte, &cardOrderObj)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		g.Dump(cardOrderObj)
		// 判断此卡是否是我们平台所开
		cardIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
			Model("fx_card_info").Where("card_id=?", cardOrderObj.CardId).Count()
		if cardIsExitNum > 0 {
			orderRecordNum, err = global.GFDB.Schema("card").Model("fx_card_order").
				Where("order_id=?", cardOrderObj.OrderId).Count()
			if orderRecordNum == 0 {
				if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_card_order").Insert(&model.FxCardOrder{
					OrderId:    cardOrderObj.OrderId,
					CardId:     cardOrderObj.CardId,
					UserReqNo:  cardOrderObj.UserReqNo,
					OpType:     cardOrderObj.OpType,
					Status:     cardOrderObj.Status,
					StatusDesc: cardOrderObj.StatusDesc,
					Amount:     cardOrderObj.Amount,
					Fee:        cardOrderObj.Fee,
				}); err != nil {
					return internalerrors.New(code.ErrUnknown, err.Error())
				}
				if cardOrderObj.OpType == 3 { // 销卡
					cardInfoObj := &model.FxCardInfo{}
					if err = global.GFDB.Ctx(ctx).Schema("card").
						Model("fx_card_info").Where("card_id=?", cardOrderObj.CardId).Scan(cardInfoObj); err != nil {
						return internalerrors.New(code.ErrUnknown, err.Error())
					}
					if _, err = global.GFDB.Ctx(ctx).Schema("card").
						Model("fx_card_info").
						Data(g.Map{"card_status": "2", "card_status_desc": "已销卡", "balance_amt": 0.00}).
						Where("card_id=?", cardOrderObj.CardId).Update(); err != nil {
						return internalerrors.New(code.ErrUnknown, err.Error())
					}
					if err == nil {
						err = RefundBalance(cardOrderObj.Amount, cardInfoObj.Account, "admin", model.Refund, "销卡退款")
					}
				}
			}
			if err == nil {
				// 发送回调给下游
				cardOrderInfoVo := &api.CardOrderInfoVo{}
				copier.Copy(cardOrderInfoVo, cardOrderObj)
				operatorCallBack(ctx, cardOrderInfoVo, cardOrderInfoVo.CardId, resp.Type)
			}
			logger.SugarLogger.Info("订单结果通知结束.......")
		} else {
			logger.SugarLogger.Infof("此卡：%s不存在与我们平台", cardOrderObj.CardId)
		}
	} else if resp.Type == "card_3ds_otp" { // 3ds通知
		logger.SugarLogger.Info("3ds通知开始.......")
		cardOtpObj := &api.CardOTPInfo{}
		jsonByte, err = json.Marshal(resp.Data)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		err = json.Unmarshal(jsonByte, &cardOtpObj)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		g.Dump(cardOtpObj)
		cardIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
			Model("fx_card_info").Where("card_id=?", cardOtpObj.CardId).Count()
		if cardIsExitNum > 0 {
			if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_card_otp").Insert(&model.FxCardOtp{
				Otp:                 cardOtpObj.Otp,
				CardId:              cardOtpObj.CardId,
				CardNo:              cardOtpObj.CardNo,
				TransactionCurrency: cardOtpObj.TransactionCurrency,
				TransactionAmount:   cardOtpObj.TransactionAmount,
				MerchantName:        cardOtpObj.MerchantName,
			}); err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			if err == nil {
				cardInfo := &model.FxCardInfo{}
				global.GFDB.Ctx(ctx).Schema("card").
					Model("fx_card_info").Where("card_id=?", cardOtpObj.CardId).Scan(&cardInfo)
				if len(cardInfo.Email) > 0 {
					// 给该卡用户发送邮件
					g.Go(ctx, func(ctx context.Context) {
						mail.SendMail(cardInfo.Email, temple.SendOtpCodeEmailStr, "OTP验证码", cardOtpObj.Otp)
					}, func(ctx context.Context, exception error) {
						logger.SugarLogger.Error(exception)
					})
				}
				// 发送回调给下游
				cardOtpObjVo := &api.CardOTPInfoVo{}
				copier.Copy(&cardOtpObjVo, &cardOtpObj)
				operatorCallBack(ctx, cardOtpObjVo, cardOtpObjVo.CardId, resp.Type)
			}
			logger.SugarLogger.Info("3ds通知结束.......")
		} else {
			logger.SugarLogger.Infof("此卡：%s不存在与我们平台", cardOtpObj.CardId)
		}
	} else if resp.Type == "trade_fee" { // 交易手续费
		logger.SugarLogger.Info("交易手续费通知开始.......")
		cardTradeFeeObj := &api.CardTradeFeeInfo{}
		jsonByte, err = json.Marshal(resp.Data)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		err = json.Unmarshal(jsonByte, &cardTradeFeeObj)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		g.Dump(cardTradeFeeObj)
		cardIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
			Model("fx_card_info").Where("card_id=?", cardTradeFeeObj.CardId).Count()
		if cardIsExitNum > 0 {
			if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_trade_fee").Insert(&model.FxTradeFee{
				CardId:    cardTradeFeeObj.CardId,
				RecordNo:  cardTradeFeeObj.RecordNo,
				ReceiptNo: cardTradeFeeObj.ReceiptNo,
				RealCcy:   cardTradeFeeObj.RealCcy,
				RealFee:   cast.ToString(cardTradeFeeObj.RealFee),
				OccurTime: cardTradeFeeObj.CreateAt,
				UserNo:    cardTradeFeeObj.UserNo,
				OpType:    cast.ToString(cardTradeFeeObj.OpType),
			}); err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			if cardTradeFeeObj.OpType == 99 {
				err = cardRefund(cast.ToString(cardTradeFeeObj.RealFee), cardTradeFeeObj.CardId)
			}
			if err == nil {
				// 发送回调给下游
				cardTradeFeeVo := &api.CardTradeFeeVo{}
				copier.Copy(&cardTradeFeeVo, &cardTradeFeeObj)
				operatorCallBack(ctx, cardTradeFeeVo, cardTradeFeeVo.CardId, resp.Type)
			}
			logger.SugarLogger.Info("交易手续费通知结束.......")
		} else {
			logger.SugarLogger.Infof("此卡：%s不存在与我们平台", cardTradeFeeObj.CardId)
		}
	}
	return nil
}

// 定时查询消费清算数据
func (c *cardCallBackSrv) CardSettlementInfo(ctx context.Context, startTime, endTime string) error {
	var (
		err             error
		url             = "/api/vas/trans/v2/query"
		dataContent     string
		pubdecrypt_str  string
		createDateStart string
		createDateEnd   string
		cardIsExitNum   int
		recordIsExitNum int
		resp            *api.SettlementInfoRes
	)
	logger.SugarLogger.Info("定时查询消费清算数据开始执行...................")
	if len(startTime) == 0 && len(endTime) == 0 {
		createDateStart = carbon.Yesterday().ToDateString()
		createDateEnd = carbon.Yesterday().ToDateString()
	} else {
		createDateStart = startTime
		createDateEnd = endTime
	}
	//获取总条数并计算分页数
	dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, &api.CardSettlementInfoReq{
		TradeType:       "SETTLEMENT",
		CardType:        "1",
		CardId:          "",
		CurrentPage:     1,
		PageSize:        50,
		CreateDateStart: createDateStart,
		CreateDateEnd:   createDateEnd,
	})
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	// 公钥解密
	pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	//计算分页数
	totalNum := 0
	if resp.SettlementInfoRespDto.TotalCount > 0 {
		if resp.SettlementInfoRespDto.TotalCount%50 == 0 {
			totalNum = resp.SettlementInfoRespDto.TotalCount / 50
		} else {
			totalNum = resp.SettlementInfoRespDto.TotalCount/50 + 1
		}
		fmt.Println("总分页数：", totalNum)
	}
	//根据分页循环请求清算退款并入库
	if totalNum > 0 {
		for i := 0; i < totalNum; i++ {
			dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, &api.CardSettlementInfoReq{
				TradeType:       "SETTLEMENT",
				CardType:        "1",
				CardId:          "",
				CurrentPage:     i + 1,
				PageSize:        50,
				CreateDateStart: createDateStart,
				CreateDateEnd:   createDateEnd,
			})
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			// 公钥解密
			pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			fmt.Printf("公钥解密:%s", pubdecrypt_str)
			err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			if len(resp.SettlementInfoRespDto.SettlementInfos) > 0 {
				for _, value := range resp.SettlementInfoRespDto.SettlementInfos {
					//if value.IsCredit == "1" {
					cardIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
						Model("fx_card_info").Where("card_id=?", value.CardId).Count()
					recordIsExitNum, err = global.GFDB.Ctx(ctx).Schema("card").
						Model("fx_card_trade_record").Where("record_no=?", value.OriginRecordNo).Count()
					if cardIsExitNum > 0 && recordIsExitNum > 0 {
						if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_card_settlement").Save(&model.FxCardSettlement{
							RecordNo:         value.RecordNo,
							CardId:           value.CardId,
							SettleDate:       value.SettleDate,
							TransCurrency:    value.TransCurrency,
							TransCurrencyAmt: cast.ToString(value.TransCurrencyAmt),
							BillCurrency:     value.BillCurrency,
							BillCurrencyAmt:  cast.ToString(value.BillCurrencyAmt),
							ApprovalCode:     value.ApprovalCode,
							IsCredit:         value.IsCredit,
							MerchantName:     value.MerchantName,
							OriginRecordNo:   value.OriginRecordNo,
						}); err != nil {
							return internalerrors.New(code.ErrUnknown, err.Error())
						}
					}
					//}
				}
			}
			logger.SugarLogger.Info("定时查询消费清算数据执行结束...................")
		}
	}

	return nil
}

// 手动触发下游失败回调
func (c *cardCallBackSrv) ManualSendCallBack(ctx context.Context) error {
	var (
		err              error
		resp             *http.Response
		body             []byte
		successNum       int
		callbackInfoList []*model.FxCallbackFailInfo
	)
	if err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_callback_fail_info").
		Where("status='fail'").Scan(&callbackInfoList); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}

	if len(callbackInfoList) > 0 {
		for {
			if err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_callback_fail_info").
				Where("status='fail'").Scan(&callbackInfoList); err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			for _, value := range callbackInfoList {
				headers := map[string]string{
					"Content-Type": "application/json",
				}
				body, err = json.Marshal(&api.SendCallBackReq{
					Type: value.Type,
					Data: value.Params,
				})
				if err != nil {
					return internalerrors.New(code.ErrUnknown, err.Error())
				}
				resp, err = httputils.HTTPPost(value.Url, headers, body)
				if resp.StatusCode == 200 && err == nil {
					if _, err = global.GFDB.Ctx(ctx).Schema("card").
						Model("fx_callback_fail_info").Data(g.Map{"status": "success"}).
						Where("id=?", value.ID).Update(); err != nil {
						return internalerrors.New(code.ErrUnknown, err.Error())
					}
					successNum++
				}
			}
			if successNum == len(callbackInfoList) {
				successNum = 0
				break
			}
		}

	}
	return err
}

// 手动补充持卡人对应卡头信息(临时的)
func (c *cardCallBackSrv) ManualOperatorCardHeader(ctx context.Context) error {
	var (
		err            error
		url            = "/api/vas/cardhodler/info"
		dataContent    string
		pubdecrypt_str string
		resp           *api.GetCardHolderRes
		cardHolderList []*model.FxCardholder
	)

	if err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_cardholder").
		Where("holder_status='1'").Scan(&cardHolderList); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	for _, obj := range cardHolderList {
		dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, &api.GetCardHolderReq{
			CardUserId: obj.CardUserId,
		})
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		// 公钥解密
		pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
		if err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
		cardHeadArray := make([]string, 0)
		for _, value := range resp.HolderCardBinsInfo {
			cardHeadArray = append(cardHeadArray, value.CardBin)
		}
		if _, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_holder_head").Insert(&model.FxHolderHead{
			CardUserId:  resp.CardUserId,
			FirstName:   resp.FirstName,
			LastName:    resp.LastName,
			CountryCode: resp.CountryCode,
			CardHead:    strings.Join(cardHeadArray, ","),
		}); err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
	}
	return err
}

func operatorCallBack(ctx context.Context, v any, cardId, respType string) {
	var (
		err      error
		jsonByte []byte
	)
	jsonByte, err = json.Marshal(v)
	if err != nil {
		logger.SugarLogger.Error(err)
		return
	}
	g.Go(ctx, func(ctx context.Context) {
		err = sendCallBack(ctx, cardId, &api.SendCallBackReq{
			Type: respType,
			Data: string(jsonByte),
		})
	}, func(ctx context.Context, exception error) {
		logger.SugarLogger.Error(exception)
	})
}

func sendCallBack(ctx context.Context, cardId string, req *api.SendCallBackReq) error {
	var (
		err              error
		cardInfoObj      = &model.FxCardInfo{}
		agentCallBackObj = &model.FxAgentCallback{}
		resp             *http.Response
		body             []byte
	)

	if err = global.GFDB.Schema("card").Model("fx_card_info").
		Where("card_id=?", cardId).Scan(&cardInfoObj); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}

	if err = global.GFDB.Schema("card").Model("fx_agent_callback").
		Where("account=?", cardInfoObj.Account).Scan(&agentCallBackObj); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if agentCallBackObj != nil {
		if len(agentCallBackObj.CallBackUrl) > 0 {
			headers := map[string]string{
				"Content-Type": "application/json",
			}
			body, err = json.Marshal(&req)
			if err != nil {
				return internalerrors.New(code.ErrUnknown, err.Error())
			}
			resp, err = httputils.HTTPPost(agentCallBackObj.CallBackUrl, headers, body)
			if resp.StatusCode != 200 || err != nil {
				// 创建一个channel接收失败的通知
				notifyChan := make(chan string)
				g.Go(ctx, func(ctx context.Context) {
					ManualSendCallBackRetryNotification(agentCallBackObj.CallBackUrl, string(body), req.Type, notifyChan)
				}, func(ctx context.Context, exception error) {
					logger.SugarLogger.Error(exception)
				})
				select {
				case failedData := <-notifyChan:
					if failedData == "fail" {
						err = createFailInfo(agentCallBackObj.CallBackUrl, agentCallBackObj.Account, string(body), req.Type, "fail")
						logger.SugarLogger.Error("回调发送失败")
					}
				}
			}
			logger.SugarLogger.Info("回调发送成功......")
		}
	}
	return err
}

func createFailInfo(url, account, data, callType, status string) error {
	var (
		err error
	)
	if _, err = global.GFDB.Schema("card").Model("fx_callback_fail_info").Insert(&model.FxCallbackFailInfo{
		Account: account,
		Url:     url,
		Type:    callType,
		Params:  data,
		Status:  status,
	}); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	return err
}

func ManualSendCallBackRetryNotification(url, body, types string, notifyChan chan<- string) {
	var (
		retryInterval []time.Duration
	)
	retryInterval = []time.Duration{
		time.Minute * 1,
		time.Minute * 2,
		time.Minute * 5,
		time.Minute * 10,
		time.Hour * 1,
		time.Hour * 2,
		time.Hour * 6,
		time.Hour * 15,
	}

	for i, delay := range retryInterval {
		logger.SugarLogger.Infof("重试第%d次", i)
		if postParamsOperator(url, types, body) == nil {
			return
		}
		time.Sleep(delay)
	}
	notifyChan <- "fail"
}

// 交易处理费
func cardRefund(refundAmount, cardId string) error {
	var (
		err               error
		url               = "/api/vas/card/refund"
		dataContent       string
		pubdecrypt_str    string
		resp              *api.CardRefundRes
		userReqNo         string
		req               = &api.CardRefundReq{}
		cardInfoObj       *model.FxCardInfo
		finalRefundAmount string
	)

	if err = requestCardInfo(cardId); err != nil {
		logger.SugarLogger.Error(err.Error())
		return internalerrors.New(code.ErrUnknown, err.Error())
	}

	if err = global.GFDB.Schema("card").Model("fx_card_info").
		Where("card_id=?", cardId).Scan(&cardInfoObj); err != nil {
		logger.SugarLogger.Error(err.Error())
		return internalerrors.New(code.ErrUnknown, err.Error())
	}

	if cardInfoObj.BalanceAmt < cast.ToFloat64(refundAmount) {
		arrearsBalance := cast.ToFloat64(refundAmount) - cardInfoObj.BalanceAmt
		logger.SugarLogger.Infof("卡所欠金额为：%f", arrearsBalance)
		g.Redis().Set(context.Background(), cardId+"-arrearsBalance", fmt.Sprintf("%.2f", arrearsBalance))
		finalRefundAmount = cast.ToString(cardInfoObj.BalanceAmt)
	} else {
		finalRefundAmount = refundAmount
	}

	userReqNo = "tpf" + "-" + utils.GetHashCode(utils.GenerateCode32())
	req.CardId = cardId
	req.UserReqNo = userReqNo
	req.RefundAmount = finalRefundAmount
	req.ChannelType = "1"
	dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, req)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	// 公钥解密
	pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if len(pubdecrypt_str) != 0 {
		if _, err = global.GFDB.Schema("card").Model("fx_card_trade_record").Insert(&model.FxCardTradeRecord{
			RecordNo:             "tpf" + "-" + utils.GetHashCode(utils.GenerateCode32()),
			CardId:               req.CardId,
			OccurTime:            carbon.Now().String(),
			TransCurrency:        "USD",
			TransCurrencyAmt:     refundAmount,
			LocalCurrency:        "USD",
			LocalCurrencyAmt:     refundAmount,
			RespCode:             "000000",
			RespCodeDesc:         "交易成功",
			MerchantName:         "FX",
			MerchantCategoryCode: "0001",
			CrossBoardType:       "1",
			TransType:            "TPFEE",
			TransStatus:          "APPROVED",
		}); err != nil {
			logger.SugarLogger.Error(err.Error())
			return internalerrors.New(code.ErrUnknown, "退款流水异常")
		}
	}
	return err
}

func postParamsOperator(url, types, data string) error {
	var (
		err  error
		resp *http.Response
		body []byte
	)
	headers := map[string]string{
		"Content-Type": "application/json",
	}
	body, err = json.Marshal(&api.SendCallBackReq{
		Type: types,
		Data: data,
	})
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	resp, err = httputils.HTTPPost(url, headers, body)
	if resp.StatusCode != 200 || err != nil {
		return errors.New("回调发送失败")
	}
	return nil
}

func requestCardInfo(cardId string) error {
	var (
		err            error
		url            = "/api/vas/card/info"
		dataContent    string
		pubdecrypt_str string
		resp           *api.CardInfoRes
		cardInfoObj    = &model.FxCardInfo{}
	)
	dataContent, err = OperatorParams(global.ViperObj.GetString("gep.url")+url, &api.GetCardInfoReq{
		CardId: cardId,
	})
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	// 公钥解密
	pubdecrypt_str, err = gorsa.PublicDecrypt(dataContent, gorsa.GetPublicKeyStr())
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	err = json.Unmarshal([]byte(pubdecrypt_str), &resp)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if err = global.GFDB.Schema("card").Model("fx_card_info").
		Where("card_id=?", cardId).
		Scan(&cardInfoObj); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	if len(pubdecrypt_str) > 0 {
		if _, err = global.GFDB.Schema("card").Model("fx_card_info").Data(g.Map{
			"start_active_date":     resp.StartActiveDate,
			"end_close_date":        resp.EndCloseDate,
			"credit_limit_amt":      resp.CreditLimitAmt,
			"min_auth_amt":          resp.MinAuthAmt,
			"max_auth_amt":          resp.MaxAuthAmt,
			"balance_amt":           resp.BalanceAmt,
			"used_auth_amt":         resp.UsedAuthAmt,
			"enable_multi_use":      resp.EnableMultiUse,
			"enable_currency_check": resp.EnableCurrencyCheck,
			"card_closed_amt":       resp.CardClosedAmt,
			"card_status":           resp.CardStatus,
			"card_status_desc":      resp.CardStatusDesc,
			"create_card_time":      resp.CreateTime,
		}).Where("card_id=?", cardId).Update(); err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}

		if _, err = global.GFDB.Schema("card").Model("fx_cardholder").Data(g.Map{
			"country":          resp.BillAddressInfo.Country,
			"country_code":     resp.BillAddressInfo.CountryCode,
			"billing_state":    resp.BillAddressInfo.BillingState,
			"billing_city":     resp.BillAddressInfo.BillingCity,
			"billing_address":  resp.BillAddressInfo.BillingAddress,
			"billing_zip_code": resp.BillAddressInfo.BillingZipCode,
			"first_name":       resp.CardUserInfo.FirstName,
			"last_name":        resp.CardUserInfo.LastName,
			//"mobile":           resp.CardUserInfo.Mobile,
			"email":      resp.CardUserInfo.Email,
			"birth_date": resp.CardUserInfo.BirthDate,
		}).Where("card_user_id=?", cardInfoObj.CardUserId).Update(); err != nil {
			return internalerrors.New(code.ErrUnknown, err.Error())
		}
	}
	return err
}
